"""
metis.py

This file is part of a program that implements a Software-Defined Radio.

Copyright (C) 2014 by G3UKB Bob Cowdery

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The authors can be reached by email at:

	bob@bobcowdery.plus.com

"""

# Python imports
import sys, os
import threading
import socket
import select
import struct
import mmap
from time import sleep
import numpy as np
import logging

# Application imports
from common.defs import *

"""
Controller for Metis hardware.
"""
class Metis:
	# Responsibilities:
	# 1. Discovery protocol.
	# 2. Start/stop Metis

	def __init__(self, q):
		"""
		Constructor
		
		Arguments:
			q	--	queue to write messages to
			
		"""
		
		self.__q = q
		
		# Set numpy to print in hex for easier debugging
		#np.set_printoptions(formatter={'int':hex})
		
		# Top and tail for Metis packets
		self.__packet_header = np.array([0xef, 0xfe], dtype=np.ubyte)
		self.__packet_tail = np.zeros((60), dtype=np.ubyte)
		
		# State
		self.__metis_sock = None
		self.__metis_address = None
		self.__mac = None
		self.__version = None
		self.__board = None
		self.__decoder = None
		
		# States
		self.IDLE = 'state_idle'
		self.CONNECTED = 'state_connected'
		self.STARTED = 'state_started'
		self.STOPPED = 'state_stopped'
		# Initial state
		self.__state = self.IDLE

	def discover(self):
		""" Execute the discovery protocol """
		
		if self.__state != self.IDLE:
			self.__q.put((BOTH, logging.INFO, 'py.metis', 'Metis is already connected!'))
		
		# Discovery packet = <<16#effe02:24, 16#00:480>>
		# Create a broadcast socket
		self.__metis_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
		self.__metis_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 
		self.__metis_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, METIS_BLK_SZ * 100)
		self.__metis_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, METIS_BLK_SZ * 10)
		# Bind on all adapters on any port
		self.__metis_sock.bind(('', 0))
		
		# Broadcast the discovery packet
		action = np.array([METIS_DISCOVER], dtype=np.ubyte)
		discovery_packet = np.concatenate((self.__packet_header, action, self.__packet_tail))
		self.__metis_sock.sendto(discovery_packet, 0, ('<broadcast>', METIS_PORT))
		
		# Wait for a response from an active Metis for METIS_TIMEOUT seconds
		data = np.zeros((60), dtype=np.ubyte)
		for n in range(3):
			(rfd, wfd, efd) = select.select([self.__metis_sock], [], [], METIS_TIMEOUT)
			if self.__metis_sock in rfd:
				# Someone responded to the broadcast
				nbytes, address = self.__metis_sock.recvfrom_into(data)
				self.__metis_address = address[0]
				self.__q.put ((BOTH, logging.INFO, 'py.metis', 'Response from IP: %s' % (str(address))))
				self.__mac = data[3:9]
				self.__version = data[9:10]
				self.__board = data[10:11]
				self.__q.put((BOTH, logging.INFO, 'py.metis', 'Response data: %s, %s, %s' % (self.__mac, self.__version, self.__board)))
				if nbytes == 60 and data[0] == 0xef and data[1] == 0xfe:
					self.__state = self.CONNECTED
					break
				
		if self.__state == self.CONNECTED:
			# Close the broadcast socket and open a new socket for communication
			self.__metis_sock.close()		
			self.__metis_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
			# Set the receiver buffer to allow 100 incoming blocks and 10 outgoing blocks before blocking. 
			self.__metis_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, METIS_BLK_SZ * 100)
			self.__metis_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, METIS_BLK_SZ * 10)
			# Bind on all adapters on any port
			self.__metis_sock.bind(('', 0))
			return True
		else:
			self.__q.put ((BOTH, logging.INFO, 'py.metis', 'Sorry, no replies received!'))
			self.__metis_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 0)
			return False

	def startMetis(self):
		""" Send the start sequence to Metis """
		
		if self.__state == self.CONNECTED or self.__state == self.STOPPED:
			self.__sequence = 0
			action = np.array([METIS_START, METIS_START_ALL], dtype=np.ubyte)
			start_packet = np.concatenate((self.__packet_header, action, self.__packet_tail))
			self.__metis_sock.sendto(start_packet, 0, (self.__metis_address, METIS_PORT))			
			self.__state = self.STARTED
			return True, self.__metis_address
		else:
			self.__q.put ((BOTH, logging.INFO, 'py.metis', 'Metis is not in the correct state for a start command!'))
			return False, None

	def stopMetis(self):
		""" Send the stop sequence to Metis """
		
		if self.__state == self.STARTED:
			action = np.array([METIS_STOP, METIS_STOP_ALL], dtype=np.ubyte)
			stop_packet = np.concatenate((self.__packet_header, action, self.__packet_tail))
			self.__metis_sock.sendto(stop_packet, 0, (self.__metis_address, METIS_PORT))
			self.__state = self.STOPPED
			return True
		else:
			self.__q.put ((BOTH, logging.INFO, 'py.metis', 'Metis is not in the correct state for a stop command!'))
			return False

	def get_connect_info(self):
		""" get Metis connection info """
		
		return self.__metis_sock, self.__metis_address, METIS_PORT
	
	
			
		